package com.quantum.util
{
	import flash.utils.ByteArray;

	import mx.utils.StringUtil;

	/**
	 * This class provides the utilities to operate on String's
	 * that are not provided by Flex.
	 */
	public class StringUtils
	{

		/**
		 * Length of names in VIM api objects
		 */
		public static const VIM_NAME_LENGTH:uint=80;

		/**
		 * Return value if the string is fine.
		 */
		public static const VIM_NAME_OK:String="vimNameValidationOk";

		/**
		 * Return value if the string contains no unicode characters, but is longer then
		 * the given number of bytes.
		 */
		public static const VIM_NAME_NO_UNICODE_TOO_LONG:String="vimNameValidationNoUnicodeTooLong";

		/**
		 * Return value if the string contains one or more unicode characters and uses
		 * more bytes in its representation then the max bytes length given.
		 */
		public static const VIM_NAME_HAS_UNICODE_TOO_LONG:String="vimNameValidationHasUnicodeTooLongRepresentation";

		/**
		 * Helper constant representing the empty string.
		 */
		public static const EMPTY:String="";

		/**
		 * Checks if a give string is null or an empty string.
		 */
		public static function isEmpty(string:String):Boolean
		{
			return ((string == null) || (string.length == 0));
		}

		/**
		 * Returns the argument string if it is not empty, otherwise returns null.
		 */
		public static function nullIfEmpty(str:String):String
		{
			if (str != null && str.length == 0)
			{
				return null;
			}
			return str;
		}

		/**
		 * Returns the first words in the given array of strings as delimited by the given
		 * delim.
		 */
		public static function getFirstWords(strings:Array /*String*/, delim:String):Array /*String*/
		{
			var words:Array /*String*/=new Array(strings.length);
			var index:int=0;

			for each (var string:String in strings)
			{
				var word:String=getFirstWord(string, delim);
				words[index++]=word;
			}
			return words;
		}

		/**
		 * Returns the first word in the text as delimited by the given delim.
		 */
		public static function getFirstWord(text:String, delim:String):String
		{
			var index:int=text.indexOf(delim);

			if (index < 0)
			{
				return text;
			}
			var word:String=text.substring(0, index);
			return word;
		}

		/**
		 * A specialized version of <code>getEvaluatableTokens</code> that returns at most one
		 * token.
		 */
		public static function getSingleEvaluatableToken(expr:String):String
		{
			var tokens:Array /*String*/=getEvaluatableTokens(expr, 1);
			if (tokens.length == 0)
			{
				return null;
			}
			return tokens[0];
		}

		/**
		 * Returns substrings that are enclosed within braces, e.g., ["foo", "bar"] for
		 * "x{foo}y{bar}z".
		 *
		 * @param count
		 *    The max number of tokens to return. All tokens are returned if -1 is specified.
		 */
		public static function getEvaluatableTokens(expr:String, count:int=-1):Array /*String*/
		{
			var tokens:Array /*String*/=[];
			var tokenStartIndex:int=expr.indexOf("{");

			while (tokenStartIndex >= 0 && (count < 0 || tokens.length < count))
			{
				var tokenEndIndex:int=expr.indexOf("}", tokenStartIndex);

				if (tokenEndIndex > 0)
				{
					var token:String=expr.substring(tokenStartIndex + 1, tokenEndIndex);
					tokens.push(token);
					tokenStartIndex=expr.indexOf("{", tokenEndIndex);
				}
				else
				{
					Assert.assert(false, "Bad eval-token format: " + expr);
					break;
				}
			}
			return tokens;
		}

		/**
		 * Checks if a string ends with a pattern.
		 *
		 * @example The following examples show how to use this method:
		 *
		 * <listing version="3.0">
		 * var str:String = "Hello, World";
		 * var result:Boolean;
		 * result = StringUtils.endsWith(str, "World"); // true
		 * result = StringUtils.endsWith(str, "world"); // false
		 * result = StringUtils.endsWith(str, "something else"); // false
		 * </listing>
		 */
		public static function endsWith(str:String, pattern:String):Boolean
		{
			return (pattern == str.substring(str.length - pattern.length));
		}

		/**
		 * Checks if a string starts with a pattern.
		 *
		 * @example The following examples show how to use this method:
		 *
		 * <listing version="3.0">
		 * var str:String = "Hello, World";
		 * var result:Boolean;
		 * result = StringUtils.startsWith(str, "Hello"); // true
		 * result = StringUtils.startsWith(str, "hello"); // false
		 * result = StringUtils.startsWith(str, "something else"); // false
		 * </listing>
		 */
		public static function startsWith(str:String, pattern:String):Boolean
		{
			return (pattern == str.substring(0, pattern.length));
		}

		/**
		 * Appends the second string after the first one with the provided separator.
		 * If one of the strings is empty the result is equal to the other one.
		 */
		public static function append(str1:String, str2:String, separator:String):String
		{
			if (isEmpty(str1))
			{
				return str2;
			}
			if (isEmpty(str2))
			{
				return str1;
			}

			return str1 + separator + str2;
		}

		/**
		 * Substitutes "substitution" tokens within the specified string
		 * with the respective arguments passed in.
		 *
		 * @param str
		 *      The string to make substitutions in.
		 *
		 * @param substitutions
		 *      Additional parameters that can be substituted in the <code>str</code>
		 * parameter at each 'substitution' location, where 'substitution' is an
		 * from one of the array elements from 'substitutions'. Each subsitition is an
		 * object with a properties 'token' and 'value' where 'token' is the string
		 * to be replaced and 'value' is the replacement.
		 *
		 * @return
		 *      New string with all of the substitution tokens replaced with the
		 * respective arguments specified.
		 */
		public static function customSubstitue(str:String, substitutions:Array):String
		{
			if (isEmpty(str))
			{
				return '';
			}
			for each (var substitution:Object in substitutions)
			{
				str=str.replace(substitution["token"], substitution["value"]);
			}
			return str;
		}

		/**
		 * Trims quotes once from the beginning and the end of the string, if found in both
		 * places.
		 *
		 * @param quoteStr
		 *    The quoting string to trim out of the given string.
		 */
		public static function trimMatchingQuotesOnce(str:String, quoteStr:String='"'):String
		{
			var index:int=str.indexOf(quoteStr);
			if (index != 0)
			{
				return str;
			}
			index=str.lastIndexOf(quoteStr);
			if (index != str.length - quoteStr.length)
			{
				return str;
			}
			var endIndex:int=str.length - quoteStr.length;
			var result:String=str.substring(quoteStr.length, endIndex);
			return result;
		}

		/**
		 * Captalize the first letter of the string.
		 * @param value
		 *    The string that needs first letter captalized.
		 */
		public static function capitalize(value:String):String
		{
			if (isEmpty(value))
			{
				return "";
			}
			var firstChar:String=value.substr(0, 1);
			firstChar=firstChar.toUpperCase();
			var otherPart:String=value.substring(1);
			value=firstChar + otherPart;
			return value;
		}

		/**
		 * Same as <code>String.split</code> but trims the result's array elements.
		 *
		 * @param str
		 *       String to split.
		 * @param delimiter
		 *       Delimiter used when splitting.
		 * @param splitEmpty
		 *       Whether to split an empty string. If this is <code>false</code> and
		 *       <code>str</code> is <code>null</code> or empty, an empty array is returned.
		 *       Default value is <code>true</code>.
		 * @return
		 *       A string array.
		 */
		public static function splitAndTrim(str:String, delimiter:String, splitEmpty:Boolean=true):Array /*String*/
		{
			if (!splitEmpty && isEmpty(str))
			{
				return [];
			}
			var result:Array=str.split(delimiter);
			var len:int=result.length;
			for (var i:int=0; i < len; i++)
			{
				result[i]=StringUtil.trim(result[i]);
			}
			return result;
		}

		/* Converts a string to a map based on the specified separators.
		 * First splits the string into entries based on the entrySeparator, then into key/value pairs
		 * based on the keyValueSeparator.
		 *
		 * @param mapString
		 *    The string which represents this map.
		 * @param entrySeparator
		 *    The string which separates entries in the map.
		 * @param keyValueSeparator
		 *    The string which separates a key/value pair.
		 * @return
		 *    The map which this string represents.
		 */
		public static function stringToMap(mapString:String, entrySeparator:String, keyValueSeparator:String):Object
		{
			if (StringUtils.isEmpty(mapString))
			{
				return null;
			}
			//Split the string based on declarations
			var entries:Array=mapString.split(entrySeparator);
			var entry:Array;
			var key:String;
			var value:String;
			var map:Object=new Object();

			for each (var entryStr:String in entries)
			{
				entry=splitAndTrim(entryStr, keyValueSeparator);
				if (entry.length == 2)
				{
					key=entry[0];
					value=entry[1];
					if (!isEmpty(key))
					{
						map[key]=value;
					}
				}
			}
			return map;
		}

		/**
		 *  Converts an array to a string with each element separated by a delimiter.
		 *
		 * @param arr
		 *    Array string objects
		 *
		 * @param delimiter
		 *    Delimiter to put between every two elements in the resulting string
		 *
		 * @return
		 *    delimiter-separated string
		 */
		public static function convertArrayToString(arr:Array /*String*/, delimiter:String=",\n"):String
		{
			var result:String="";

			if (arr == null || arr.length == 0)
			{
				return result;
			}

			var first:Boolean=true;

			for each (var str:String in arr)
			{
				if (first)
				{
					result=result + str;
					first=false;
				}
				else
				{
					result=result.concat(delimiter, str);
				}
			}

			return result;
		}

		/**
		 * Validate VIM object name against the number of bytes it may use in its
		 * representation.
		 *
		 * @param name
		 *       The input name to be validated
		 *
		 * @param byteLength
		 *       The max number of bytes the name may use in its representation
		 *
		 * @return
		 *       <code>VIM_NAME_OK</code> if the name is valid.
		 *       <code>VIM_NAME_NO_UNICODE_TOO_LONG</code> if the name contains only ASCII
		 *       characters, but is too long. <code>VIM_NAME_HAS_UNICODE_TOO_LONG</code> if
		 *       the name contains any unicode characters and uses more bytes then the
		 *       byteLength.
		 */
		public static function validateVimName(name:String, byteLength:uint=VIM_NAME_LENGTH):String
		{
			var barray:ByteArray=new ByteArray();
			barray.writeUTFBytes(name); // This is UFT-8 with variable sized characters.
			if (barray.length > byteLength)
			{
				if (barray.length == name.length)
				{
					return VIM_NAME_NO_UNICODE_TOO_LONG;
				}
				else
				{
					return VIM_NAME_HAS_UNICODE_TOO_LONG;
				}
			}
			return VIM_NAME_OK;
		}
	} // class
} // package
